package util.qualitycontainers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.TreeSet;
import util.qualitycontainers.qualities.IMorphQuality;


/**
 * A container designed to carry and retrieve any number of "Qualities"
 * 
 * @author Julian
 */
public abstract class MorphableObject extends Observable
    implements Comparable<MorphableObject>, Cloneable
{

    protected List<IMorphQuality> myQualities;
    protected boolean amActive;


    /**
     * Creates and empty IQualityContainer
     */
    public MorphableObject ()
    {
        this(new IMorphQuality[0]);
    }


    /**
     * A IQualityContainerObject initialized with the default versions of these
     * qualities.
     * 
     * @param qualities
     */
    public MorphableObject (Class<? extends IMorphQuality> ... qualities)
    {
        this();
        replaceAllQualities(qualities);

    }


    /**
     * A IQualityContainerObject initialized with these exact qualities.
     * 
     * @param qualities
     */
    public MorphableObject (IMorphQuality ... qualities)
    {
        replaceAllQualities(qualities);
    }


    /**
     * activate this container
     */
    public void activate ()
    {
        amActive = true;
    }


    /**
     * deactivate this container
     */
    public void deactivate ()
    {
        amActive = false;
    }


    /**
     * Check to see if this container is active
     * 
     * @return
     */
    public boolean isActive ()
    {
        return amActive;
    }


    /**
     * Returns this Container's list of qualities
     * 
     * @return
     */
    public List<IMorphQuality> getQualities ()
    {
        return myQualities;
    }


    /**
     * Retrieves the quality contained in this wrapper that corresponds to in
     * the input IQuality's class
     * 
     * @param quality
     * @return
     */
    public IMorphQuality getQuality (IMorphQuality quality)
    {
        return this.getQuality(quality.getClass());
    }


    /**
     * Retrieves the first quality contained in this wrapper that corresponds to
     * in this Class<? extends IQuality>
     * 
     * @param cls
     * @return
     */
    public IMorphQuality getQuality (Class<? extends IMorphQuality> cls)
    {
        for (IMorphQuality mq : myQualities)
        {
            if (mq.isSameQualityClass(cls))
            {
                return mq;
            }
        }
        return null;
    }


    /**
     * Sets this container's qualities to an exact clone of the input
     * 
     * @param qualities
     */
    public void replaceAllQualities (IMorphQuality ... qualities)
    {
        myQualities = new ArrayList<IMorphQuality>();
        myQualities.addAll(Arrays.asList(qualities));
    }


    /**
     * Sets this container's qualities to an exact clone of the input
     * 
     * @param qualities
     */
    public void replaceAllQualities (Class<? extends IMorphQuality> ... qualities)
    {
        myQualities = new ArrayList<IMorphQuality>();
        for (Class<? extends IMorphQuality> q : qualities)
            try
            {
                myQualities.add(q.newInstance());
            }
            catch (Exception e)
            {
                throw new MorphException(MorphException.NO_DEFAULT_CONSTRUCTOR);
            }
    }


    /**
     * Sets this selection of the container's qualities to the default of all
     * the input quality classes
     * 
     * @param qualities
     */
    public void setQualities (IMorphQuality ... quals)
    {
        for (IMorphQuality q : quals)
            this.getQuality(q).setTo(q);
    }


    /**
     * Adds a default version of each quality in this list to this Container
     * 
     * @param q
     */
    public void addQualities (Class<? extends IMorphQuality> ... qualities)
    {
        for (Class<? extends IMorphQuality> c : qualities)
        {
            this.addQuality(c);
        }

    }


    /**
     * Adds a default version of each quality in this list to this Container
     * 
     * @param q
     */
    public void addQualities (IMorphQuality ... qualities)
    {
        myQualities.addAll(Arrays.asList(qualities));
    }


    /**
     * Adds a clone of this quality to this Container
     * 
     * @param q
     */
    public void addQuality (IMorphQuality q)
    {
        myQualities.add(q);
    }


    /**
     * Adds a default version of this quality to this Container
     * 
     * @param q
     */
    public void addQuality (Class<? extends IMorphQuality> q)
    {
        try
        {
            myQualities.add(q.newInstance());
        }
        catch (Exception e)
        {
            throw new MorphException(MorphException.EMPTY_CONSTRUCTOR_DNE);
        }
    }


    /**
     * Remove first instance of these qualities from this Container
     * 
     * @param q
     */
    public void removeQuality (IMorphQuality ... quals)
    {
        for (IMorphQuality q : quals)
            removeQuality(q);
    }


    /**
     * remove a single quality from this container. Targeting by CLASS of input
     * 
     * @param q
     */
    @SuppressWarnings("unchecked")
    public void removeQuality (IMorphQuality q)
    {
        removeQuality(q.getClass());
    }


    /**
     * Remove the first instance of the quality of this type from this
     * Container. returns true if container held all qualities removed
     * 
     * @param q
     */
    public boolean removeQuality (Class<? extends IMorphQuality> ... quals)
    {
        boolean b = true;
        for (Class<? extends IMorphQuality> q : quals)
            if (this.carriesQuality(q)) myQualities.remove(this.getQuality(q));
            else b = false;
        return b;
    }


    /**
     * replaces the first quality, toReplace, with a clone of the second
     * quality, replaceWith
     * 
     * @param toReplace = quality to be replaced
     * @param replaceWith = quality to add
     */
    public void replaceQuality (IMorphQuality toReplace,
                                IMorphQuality replaceWith)
    {

    }


    /**
     * replaces the first quality, toReplace, with a clone of the second
     * quality, replaceWith
     * 
     * @param toReplace = quality to be replaced
     * @param replaceWith = quality to add
     */
    public void replaceQuality (Class<? extends IMorphQuality> toReplace,
                                IMorphQuality replaceWith)
    {
        this.removeQuality(toReplace);
        this.addQuality(replaceWith);
    }


    /**
     * replaces the first quality (retrieved by class), toReplace, with second
     * quality, replaceWith
     * 
     * @param toReplace = quality to be replaced
     * @param replaceWith = quality to add
     */
    public void replaceQuality (Class<? extends IMorphQuality> toReplace,
                                Class<? extends IMorphQuality> replaceWith)
    {
        this.removeQuality(toReplace);
        this.addQuality(replaceWith);
    }


    /**
     * Returns true this Container carries a quality of the same class as the
     * input quality
     * 
     * @param obj
     * @return
     */
    public boolean carriesQuality (IMorphQuality obj)
    {
        return carriesQuality(obj.getClass());
    }


    /**
     * Returns true this Container carries a quality of the same class as the
     * input class
     * 
     * @param obj
     * @return
     */
    public boolean carriesQuality (Class<? extends IMorphQuality> cls)
    {
        if (getQuality(cls) == null) return false;
        return true;
    }


    /**
     * Returns true this Container carries a quality of the same class AND it
     * exactly equals the input
     * 
     * @param obj
     * @return
     */
    public boolean carriesExactQuality (IMorphQuality qual)
    {
        if (this.carriesQuality(qual.getClass())) return this.getQuality(qual)
                                                             .equals(qual);
        return false;
    }


    /**
     * Returns true this Container carries all quality of the same class as the
     * input classes
     * 
     * @param obj
     * @return
     */
    public boolean carriesQualities (IMorphQuality ... quals)
    {
        for (IMorphQuality q : quals)
        {
            if (!this.carriesQuality(q)) return false;
        }
        return true;
    }


    /**
     * Returns true this Container carries all quality of the same class as the
     * input classes
     * 
     * @param obj
     * @return
     */
    public boolean carriesQualities (Class<? extends IMorphQuality> ... classes)
    {
        for (Class<? extends IMorphQuality> c : classes)
        {
            if (!this.carriesQuality(c)) return false;
        }
        return true;
    }


    /**
     * Resets this container to its default version (from empty constructor)
     */
    public void reset ()
    {

    }


    /**
     * removes all qualities from this wrapper
     * 
     * @return
     */
    public void clear ()
    {
        myQualities = new ArrayList<IMorphQuality>();
    }


    public static List<Class<? extends IMorphQuality>> convertToClassList (List<IMorphQuality> quals)
    {
        List<Class<? extends IMorphQuality>> cls =
            new ArrayList<Class<? extends IMorphQuality>>();
        for (IMorphQuality q : quals)
            cls.add(q.getClass());
        return cls;
    }


    protected void canCompare (MorphableObject o)
    {
        this.carriesQualities((IMorphQuality[]) o.getQualities().toArray());

    }


    @Override
    public MorphableObject clone ()
    {
        try
        {
            return this.getClass()
                       .getConstructor(TreeSet.class)
                       .newInstance(myQualities);
        }
        catch (Exception e)
        {
            throw new MorphException(MorphException.ERROR_IN_CLONING);
        }
    }

}
